Restful一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制:
CORS CORS是现代浏览亲支持快于请求的一种方式,全称是“跨域资源共享“(Cross-origin resource sharing),当使用XMLHttpRequest发送请求时,浏览器发现该请求不符合同源测率,会给该请求头:Origin,后台进行以系列处理,如果确定请求则在返回结果加入一个响应头:Access-Control-Allow-Origin;浏览器判断该响应头中是否包含Origin的值,如果有浏览器会处理响应,我们就可以拿到响应数据,如果不包含浏览器之间驳回,这时我们无法拿到响应数据
JWT :Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密;
FastDFS :FastDFS 是用 c 语言编写的一款开源的分布式文件系统。FastDFS 为互联网量身定制, 充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用 FastDFS 很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。
FastDFS 架构包括 Tracker server 和 Storage server。客户端请求 Tracker server 进行文 件上传、下载,通过 Tracker server 调度最终由 Storage server 完成文件上传和下载。

[TOC]

Django认证系统|跨域请求|Celery

提供了用户模型类User和User的相关操作方法。

自定义User模型类之后,需要设置配置项AUTH_USER_MODEL

1. 前后端域名设置

域名和IP是对应的关系。

DNS解析:获取域名对应的IP

通过域名访问网站时,先到本地的hosts中查找IP和域名的对应关系,如果查到直接根据IP访问网站,如果查不到再进行DNS域名解析。

前端服务器域名: www.meiduo.site

后端API服务器域名:api.meiduo.site

2. 短信验证码

1
2
3
4
5
6
URL: GET /sms_codes/(?P<mobile>1[3-9]\d{9})/
参数: url地址中传递mobile
响应:
{
"message": "OK"
}

3. 跨域请求

浏览器的同源策略: 协议、主机IP和端口PORT相同的地址是同源,否则是非同源

当发起请求的页面地址和被请求的地址不是同源,那么这个请求就是跨域请求

在发起请求时,如果浏览器发现请求是跨域请求,那么在请求的报文头中,会添加如下信息:

Origin: 源请求IP地址

例如:Origin: http://www.meiduo.site:8080

在被请求的服务器返回的响应中,如果响应头中包含如下信息:

Access-Control-Allow-Origin: 源请求IP地址

例如:Access-Control-Allow-Origin: http://www.meiduo.site:8080

那么浏览器认为被请求服务器支持来源地址对其进行跨域请求,否则认为不支持,浏览器会将请求直接驳回。

Django跨域请求扩展使用。

4. celery异步任务队列

本质:

​ 使用进程或协程调用函数实现异步。

基本概念:

​ 发出者:发出所有执行的任务(任务就是函数)。

​ (中间人)任务队列:存放所要执行的任务信息。

​ 处理者:也就是工作的进程或协程,负责监听任务队列,发现任务便执行对应的任务函数。

特点:

​ 1)任务发送者和处理者可以分布在不同的电脑上,通过中间人进行信息交换。

​ 2)任务队列中的任务会进行排序,先添加的任务会被先执行。

使用:

​ 1)安装 pip install celery

​ 2)创建Celery对象并配置中间人地址

​ from celery import Celery

​ celery_app = Celery(‘demo’)

​ 配置文件:broker_url=’中间人地址’

​ celery_app.config_from_object(‘配置文件路径’)

​ 3)定义任务函数

​ @celery_app.task(name=’my_first_task’)

​ def my_task(a, b):

​ print(‘任务函数被执行’)

​ …

​ 4)启动worker

​ celery -A ‘celery_app文件路径’ worker -l info

​ 5)发出任务

​ my_task.delay(2, 3)

5. 用户注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
URL: POST /users/
参数:
{
"username": "用户名",
"password": "密码",
"password2": "重复密码",
"mobile": "手机号",
"sms_code": "短信验证码",
"allow": "是否同意协议"
}
响应:
{
"id": "用户id",
"username": "用户名",
"mobile": "手机号"
}

补充(axios请求发送)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
axios.get('url地址', [config])
.then(response => {
// 请求成功,可通过response.data获取响应数据
})
.catch(error => {
// 请求失败,可通过error.response获取响应对象
// error.response.data获取响应数据
})

axios.post('url地址', [data], [config])
.then(response => {
// 请求成功,可通过response.data获取响应数据
})
.catch(error => {
// 请求失败,可通过error.response获取响应对象
// error.response.data获取响应数据
})

注册|JWT Token|普通登录

1. 用户注册
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
URL: POST /users/
参数:
{
"username": "用户名",
"password": "密码",
"password2": "重复密码",
"mobile": "手机号",
"sms_code": "短信验证码",
"allow": "是否同意协议"
}
响应:
{
"id": "用户id",
"username": "用户名",
"mobile": "手机号"
}

创建新用户:User.objects.create_user(username, email=None, password=None, *\extra_fields*)

2. JWT token

1)session认证

1
2
3
4
5
6
7
1. 接收用户名和密码
2. 判断用户名和密码是否正确
3. 保存用户的登录状态(session)
session['user_id'] = 1
session['username'] = 'smart'
session['mobile'] = '13155667788'
4. 返回应答,登录成功

session认证的一些问题:

  1. session存储在服务器端,如果登录的用户过多,服务器开销比较大。
  2. session依赖于cookie,session的标识存在cookie中,可能会有CSRF(跨站请求伪造)。

2)jwt 认证机制(替代session认证)

1
2
3
4
1. 接收用户名和密码
2. 判断用户名和密码是否正确
3. 生成(签发)一个jwt token(token中保存用户的身份信息) 公安局(服务器)=>签发身份证(jwt token)
4. 返回应答,将jwt token信息返回给客户端。

如果之后需要进行身份认证,客户端需要将jwt token发送给服务器,由服务器验证jwt token的有效性。

3)jwt 的数据格式

1
2
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ik
pvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFO

a)headers头部

1
2
3
4
{
"token类型声明",
"加密算法"
}

base64加密(很容易被解密)

b)payload(载荷):用来保存有效信息

1
2
3
4
5
6
{
"user_id": 1,
"username": "smart",
"mobile": "13155667788",
"exp": "有效时间"
}

base64加密

c)signature(签名):防止jwt token被伪造

将headers和payload进行拼接,用.隔开,使用一个密钥(secret key)进行加密,加密之后的内容就是签名。

jwt token是由服务器生成,密钥保存在服务器端。

3. jwt 扩展签发jwt token
1
2
3
4
5
6
7
from rest_framework_jwt.settings import api_settings

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
4. 用户登录

1)jwt 扩展的登录视图obtain_jwt_token:

​ 接收username和password,进行用户名和密码验证,正确情况下签发jwt token并返回给客户端。

2)更改obtain_jwt_token组织响应数据的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def jwt_response_payload_handler(token, user=None, request=None):
"""
自定义jwt认证成功返回数据
"""
return {
'token': token,
'user_id': user.id,
'username': user.username
}

# JWT扩展配置
JWT_AUTH = {
# ...
'JWT_RESPONSE_PAYLOAD_HANDLER': 'users.utils.jwt_response_payload_handler',
}

3)登录支持用户名和手机号

1
2
3
obtain_jwt_token
-> from django.contrib.auth import authenticate
-> from django.contrib.auth.backends import ModelBackend(authenticate: 根据用户名和密码进行校验)

自定义Django的认证系统后端,同时设置配置项AUTHENTICATION_BACKENDS

##QQ登录

1. QQ登录-预备工作

1)成为QQ开发者

2)创建开发者应用

3)查询QQ登录开发文档

2. QQ登录-开发关键点

获取QQ登录用户的唯一身份标识(openid),然后根据openid进行处理。

判断该QQ用户是否绑定过本网站的用户,如果绑定过,直接登录,如果未绑定过,先进行绑定。

QQ用户绑定: 将openid 和 用户user_id 对应关系存下来。

id user_id openid
1 2 Akdk19389kDkdkk99939kdk
2 2 AKdkk838e8jdiafkdkkFKKKFf

注:一个用户可以绑定多个qq账户。

3. QQ登录API

1)获取QQ登录网址API

1
2
3
4
5
API: GET /oauth/qq/authorization/?next=<url>
响应:
{
"login_url": "QQ登录网址"
}

2)获取QQ登录用户openid并进行处理API

1
2
3
4
5
6
7
8
9
10
11
12
13
API: GET /oauth/qq/user/?code=<code>
参数: code
响应:
1)如果openid已经绑定过本网站的用户,直接签发jwt token,返回
{
'user_id': <用户id>,
'username': <用户名>,
'token': <token>
}
2)如果openid没有绑定过本网站的用户,先对openid进行加密生成token,把加密的内容返回
{
'access_token': <token>
}

3)绑定QQ登录用户的信息API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
API: POST /oauth/qq/user/
参数:
{
"mobile": <手机号>,
"password": <密码>,
"sms_code": <短信验证码>,
"access_token": <access_token>
}
响应:
{
'id': <用户id>,
'username': <用户名>,
'token': <token>
}

###4. 相关模块的使用

1
2
3
4
5
6
7
8
9
10
11
# 将python字典转化为查询字符串
from urllib.parse import urlencode

# 将查询字符串转换成python字典
from urllib.parse import parse_qs

# 发起网络请求
from urllib.request import urlopen

# itsdangerous: 加密和解密
from itsdangerous import TimedJSONWebSignatureSerializer

邮件发送|省市三级联动

个人信息获取

1)给User添加email_active字段,用于记录邮箱email是否被激活。

2)API接口:获取用户个人信息。

1
2
3
4
5
6
7
8
9
10
11
12
请求方式和URL地址: GET /user/
前端传递参数:
在请求头中携带jwt token。
返回值:
{
"id": "<用户id>",
"username": "<用户名>",
"mobile": "<手机号>",
"email": "<邮箱>",
"email_active": "<激活标记>"
}
注: DRF中`JSONWebTokenAuthentication`认证机制会根据jwt token对用户身份进行认证,如果认证失败返回401错误,如果是权限禁止返回403错误。

3)使用RetrieveAPIView时,其获取单个对象时是根据pk获取,我们这里所有获取的是当前登录的用户,所以需要把get_object方法进行重写。

4)request对象的user属性。

对于request对象,有一个user属性。这个user属性:

​ a)如果用户认证成功,request.user是User模型类的实例对象,存放的是当前登录用户的信息。

​ b)如果用户未认证,request.userAnonymousUser类的实例对象。

###用户邮箱设置

1)API接口:设置用户个人邮箱。

1
2
3
4
5
6
7
8
9
请求方式和URL地址: PUT /email/
前端传递参数:
1)在请求头中携带jwt token。
2)邮箱email
返回值:
{
"id": "<用户id>",
"email": "<邮箱>",
}

2)处理流程:

​ a)接收参数并进行校验(email是否传递,格式是否正确)

​ b)保存用户的邮箱信息并发送激活邮件

​ c)返回应答,设置邮箱成功

3)发送激活邮件

​ a)生成激活链接:在激活链接中需要保存待激活用户的id和email,但是为了安全,需要对信息进行加密。

​ b)发送邮件:配置文件中先进行邮件发送配置,在使用django的send_mail方法发送邮件,为了不影响邮件设置过程,邮件采用celery发送。

1
2
from django.core.mail import send_mail
send_mail(subject='邮件主题', message='正文', from_email='发件人', recipient_list='收件人列表', html_message='html邮件正文')

个人邮箱激活

1)API接口:激活用户个人邮箱。

1
2
3
4
5
6
7
请求方式和URL地址: GET /emails/verification/?token=xxx
前端传递参数:
1)token
返回值:
{
"message": "处理结果"
}

2)处理流程:

​ a)接收参数token并进行校验(token是否传递,token是否有效)

​ b)将用户邮箱激活标记设置为已激活。

​ c)返回应答,邮箱激活成功。

省市县三级信息

1)信息存储(自关联) 地区的自关联其实是一个特殊一对多的关系。

一个省包含很多个市,一个市包含很多县。

id(地区id) name(地区名) parent_id(父级地区ID)
320000 江苏省 NULL
320200 无锡市 320000
320282 宜兴市 320200

2)模型类自关联(地区的自关联其实是一个特殊的一对多)

1
2
3
4
5
6
class Area(models.Model):
"""
行政区域
"""
name = models.CharField(max_length=20, verbose_name='名称')
parent = models.ForeignKey('self', on_delete=models.SET_NULL, related_name='subs', null=True, blank=True, verbose_name='上级行政区域')

注:模型类自关联,ForeignKey第一个参数传self

另外之前的关联查询中,有了一个area对象之后,查询关联的下级地区信息和父级地区,例如:

1
2
3
area = Area.objects.get(id='320200')
area.parent # 父级地区,由多查一,对象名.关联属性
area.area_set.all() # 子级地区,由一查多,对象名.多类名_set.all()

当ForeignKey创建关联属性时,指定了related_name='subs'之后,再查询和area对象关联的子级地区,不在使用area.area_set.all(),而是使用area.subs.all()

3)定义导入地区信息shell脚本

1
2
#! /bin/bash
mysql -u'<用户名>' -p'<密码>' -h'<数据库主机IP>' '<数据库名>' < '<sql文件>'

4)地区视图集。

补充(客户端发送请求时携带JWT token数据)

1
2
3
4
5
6
7
8
9
10
11
12
13
axios.get(this.host + '/user/', {
headers: {
// 通过请求头向后端传递JWT token的方法
'Authorization': 'JWT ' + <JWT token数据>
},
responseType: 'json',
})
.then(response => {
...
})
.catch(error => {
...
});
× 请我吃糖~
打赏二维码